/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *//* * Portable safe sprintf code. * * Author: Kipp E.B. Hickman */#include"mozilla/AllocPolicy.h"#include"mozilla/Printf.h"#include"mozilla/Sprintf.h"#include"mozilla/UniquePtrExtensions.h"#include"mozilla/Vector.h"#include<stdarg.h>#include<stdio.h>#include<stdlib.h>#include<string.h>#if defined(XP_WIN)#include<windows.h>#endif/* * Note: on some platforms va_list is defined as an array, * and requires array notation. */#ifdef HAVE_VA_COPY#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo, bar)#elif defined(HAVE_VA_LIST_AS_ARRAY)#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]#else#define VARARGS_ASSIGN(foo, bar) (foo) = (bar)#endif/* * Numbered Argument State */structNumArgState{inttype;// type of the current apva_listap;// point to the corresponding position on ap};typedefmozilla::Vector<NumArgState,20,mozilla::MallocAllocPolicy>NumArgStateVector;#define TYPE_SHORT 0#define TYPE_USHORT 1#define TYPE_INTN 2#define TYPE_UINTN 3#define TYPE_LONG 4#define TYPE_ULONG 5#define TYPE_LONGLONG 6#define TYPE_ULONGLONG 7#define TYPE_STRING 8#define TYPE_DOUBLE 9#define TYPE_INTSTR 10#define TYPE_POINTER 11#if defined(XP_WIN)#define TYPE_WSTRING 12#endif#define TYPE_UNKNOWN 20#define FLAG_LEFT 0x1#define FLAG_SIGNED 0x2#define FLAG_SPACED 0x4#define FLAG_ZEROS 0x8#define FLAG_NEG 0x10// Fill into the buffer using the data in srcboolmozilla::PrintfTarget::fill2(constchar*src,intsrclen,intwidth,intflags){charspace=' ';width-=srclen;if(width>0&&(flags&FLAG_LEFT)==0){// Right adjustingif(flags&FLAG_ZEROS)space='0';while(--width>=0){if(!emit(&space,1))returnfalse;}}// Copy out the source dataif(!emit(src,srclen))returnfalse;if(width>0&&(flags&FLAG_LEFT)!=0){// Left adjustingwhile(--width>=0){if(!emit(&space,1))returnfalse;}}returntrue;}/* * Fill a number. The order is: optional-sign zero-filling conversion-digits */boolmozilla::PrintfTarget::fill_n(constchar*src,intsrclen,intwidth,intprec,inttype,intflags){intzerowidth=0;intprecwidth=0;intsignwidth=0;intleftspaces=0;intrightspaces=0;intcvtwidth;charsign;if((type&1)==0){if(flags&FLAG_NEG){sign='-';signwidth=1;}elseif(flags&FLAG_SIGNED){sign='+';signwidth=1;}elseif(flags&FLAG_SPACED){sign=' ';signwidth=1;}}cvtwidth=signwidth+srclen;if(prec>0){if(prec>srclen){precwidth=prec-srclen;// Need zero fillingcvtwidth+=precwidth;}}if((flags&FLAG_ZEROS)&&(prec<0)){if(width>cvtwidth){zerowidth=width-cvtwidth;// Zero fillingcvtwidth+=zerowidth;}}if(flags&FLAG_LEFT){if(width>cvtwidth){// Space filling on the right (i.e. left adjusting)rightspaces=width-cvtwidth;}}else{if(width>cvtwidth){// Space filling on the left (i.e. right adjusting)leftspaces=width-cvtwidth;}}while(--leftspaces>=0){if(!emit(" ",1))returnfalse;}if(signwidth){if(!emit(&sign,1))returnfalse;}while(--precwidth>=0){if(!emit("0",1))returnfalse;}while(--zerowidth>=0){if(!emit("0",1))returnfalse;}if(!emit(src,uint32_t(srclen)))returnfalse;while(--rightspaces>=0){if(!emit(" ",1))returnfalse;}returntrue;}/* Convert a long into its printable form. */boolmozilla::PrintfTarget::cvt_l(longnum,intwidth,intprec,intradix,inttype,intflags,constchar*hexp){charcvtbuf[100];char*cvt;intdigits;// according to the man page this needs to happenif((prec==0)&&(num==0))returntrue;// Converting decimal is a little tricky. In the unsigned case we// need to stop when we hit 10 digits. In the signed case, we can// stop when the number is zero.cvt=cvtbuf+sizeof(cvtbuf);digits=0;while(num){intdigit=(((unsignedlong)num)%radix)&0xF;*--cvt=hexp[digit];digits++;num=(long)(((unsignedlong)num)/radix);}if(digits==0){*--cvt='0';digits++;}// Now that we have the number converted without its sign, deal with// the sign and zero padding.returnfill_n(cvt,digits,width,prec,type,flags);}/* Convert a 64-bit integer into its printable form. */boolmozilla::PrintfTarget::cvt_ll(int64_tnum,intwidth,intprec,intradix,inttype,intflags,constchar*hexp){// According to the man page, this needs to happen.if(prec==0&&num==0)returntrue;// Converting decimal is a little tricky. In the unsigned case we// need to stop when we hit 10 digits. In the signed case, we can// stop when the number is zero.int64_trad=int64_t(radix);charcvtbuf[100];char*cvt=cvtbuf+sizeof(cvtbuf);intdigits=0;while(num!=0){int64_tquot=uint64_t(num)/rad;int64_trem=uint64_t(num)%rad;int32_tdigit=int32_t(rem);*--cvt=hexp[digit&0xf];digits++;num=quot;}if(digits==0){*--cvt='0';digits++;}// Now that we have the number converted without its sign, deal with// the sign and zero padding.returnfill_n(cvt,digits,width,prec,type,flags);}/* * Convert a double precision floating point number into its printable * form. */boolmozilla::PrintfTarget::cvt_f(doubled,constchar*fmt0,constchar*fmt1){charfin[20];// The size is chosen such that we can print DBL_MAX. See bug#1350097.charfout[320];intamount=fmt1-fmt0;MOZ_ASSERT((amount>0)&&(amount<(int)sizeof(fin)));if(amount>=(int)sizeof(fin)){// Totally bogus % command to sprintf. Just ignore itreturntrue;}memcpy(fin,fmt0,(size_t)amount);fin[amount]=0;// Convert floating point using the native snprintf code#ifdef DEBUG{constchar*p=fin;while(*p){MOZ_ASSERT(*p!='L');p++;}}#endifsize_tlen=SprintfLiteral(fout,fin,d);MOZ_RELEASE_ASSERT(len<=sizeof(fout));returnemit(fout,len);}/* * Convert a string into its printable form. "width" is the output * width. "prec" is the maximum number of characters of "s" to output, * where -1 means until NUL. */boolmozilla::PrintfTarget::cvt_s(constchar*s,intwidth,intprec,intflags){if(prec==0)returntrue;if(!s)s="(null)";// Limit string length by precision valueintslen=int(strlen(s));if(0<prec&&prec<slen)slen=prec;// and away we goreturnfill2(s,slen,width,flags);}/* * BuildArgArray stands for Numbered Argument list Sprintf * for example, * fmp = "%4$i, %2$d, %3s, %1d"; * the number must start from 1, and no gap among them */staticboolBuildArgArray(constchar*fmt,va_listap,NumArgStateVector&nas){size_tnumber=0,cn=0,i;constchar*p;charc;// First pass:// Detemine how many legal % I have got, then allocate space.p=fmt;i=0;while((c=*p++)!=0){if(c!='%')continue;if((c=*p++)=='%')// skip %% casecontinue;while(c!=0){if(c>'9'||c<'0'){if(c=='$'){// numbered argument caseif(i>0)MOZ_CRASH("Bad format string");number++;}else{// non-numbered argument caseif(number>0)MOZ_CRASH("Bad format string");i=1;}break;}c=*p++;}}if(number==0)returntrue;// Only allow a limited number of arguments.MOZ_RELEASE_ASSERT(number<=20);if(!nas.growByUninitialized(number))returnfalse;for(i=0;i<number;i++)nas[i].type=TYPE_UNKNOWN;// Second pass:// Set nas[].type.p=fmt;while((c=*p++)!=0){if(c!='%')continue;c=*p++;if(c=='%')continue;cn=0;while(c&&c!='$'){// should improve error check latercn=cn*10+c-'0';c=*p++;}if(!c||cn<1||cn>number)MOZ_CRASH("Bad format string");// nas[cn] starts from 0, and make sure nas[cn].type is not assigned.cn--;if(nas[cn].type!=TYPE_UNKNOWN)continue;c=*p++;// flagswhile((c=='-')||(c=='+')||(c==' ')||(c=='0')){c=*p++;}// widthif(c=='*'){// not supported feature, for the argument is not numberedMOZ_CRASH("Bad format string");}while((c>='0')&&(c<='9')){c=*p++;}// precisionif(c=='.'){c=*p++;if(c=='*'){// not supported feature, for the argument is not numberedMOZ_CRASH("Bad format string");}while((c>='0')&&(c<='9')){c=*p++;}}// sizenas[cn].type=TYPE_INTN;if(c=='h'){nas[cn].type=TYPE_SHORT;c=*p++;}elseif(c=='L'){nas[cn].type=TYPE_LONGLONG;c=*p++;}elseif(c=='l'){nas[cn].type=TYPE_LONG;c=*p++;if(c=='l'){nas[cn].type=TYPE_LONGLONG;c=*p++;}}elseif(c=='z'||c=='I'){static_assert(sizeof(size_t)==sizeof(int)||sizeof(size_t)==sizeof(long)||sizeof(size_t)==sizeof(longlong),"size_t is not one of the expected sizes");nas[cn].type=sizeof(size_t)==sizeof(int)?TYPE_INTN:sizeof(size_t)==sizeof(long)?TYPE_LONG:TYPE_LONGLONG;c=*p++;}// formatswitch(c){case'd':case'c':case'i':break;case'o':case'u':case'x':case'X':// Mark as unsigned type.nas[cn].type|=1;break;case'e':case'f':case'g':nas[cn].type=TYPE_DOUBLE;break;case'p':nas[cn].type=TYPE_POINTER;break;case'S':#if defined(XP_WIN)nas[cn].type=TYPE_WSTRING;#elseMOZ_ASSERT(0);nas[cn].type=TYPE_UNKNOWN;#endifbreak;case's':#if defined(XP_WIN)if(nas[cn].type==TYPE_LONG){nas[cn].type=TYPE_WSTRING;break;}#endif// Other type sizes are not supported here.MOZ_ASSERT(nas[cn].type==TYPE_INTN);nas[cn].type=TYPE_STRING;break;case'n':nas[cn].type=TYPE_INTSTR;break;default:MOZ_ASSERT(0);nas[cn].type=TYPE_UNKNOWN;break;}// get a legal para.if(nas[cn].type==TYPE_UNKNOWN)MOZ_CRASH("Bad format string");}// Third pass:// Fill nas[].ap.cn=0;while(cn<number){// A TYPE_UNKNOWN here means that the format asked for a// positional argument without specifying the meaning of some// earlier argument.MOZ_ASSERT(nas[cn].type!=TYPE_UNKNOWN);VARARGS_ASSIGN(nas[cn].ap,ap);switch(nas[cn].type){caseTYPE_SHORT:caseTYPE_USHORT:caseTYPE_INTN:caseTYPE_UINTN:(void)va_arg(ap,int);break;caseTYPE_LONG:(void)va_arg(ap,long);break;caseTYPE_ULONG:(void)va_arg(ap,unsignedlong);break;caseTYPE_LONGLONG:(void)va_arg(ap,longlong);break;caseTYPE_ULONGLONG:(void)va_arg(ap,unsignedlonglong);break;caseTYPE_STRING:(void)va_arg(ap,char*);break;caseTYPE_INTSTR:(void)va_arg(ap,int*);break;caseTYPE_DOUBLE:(void)va_arg(ap,double);break;caseTYPE_POINTER:(void)va_arg(ap,void*);break;#if defined(XP_WIN)caseTYPE_WSTRING:(void)va_arg(ap,wchar_t*);break;#endifdefault:MOZ_CRASH();}cn++;}returntrue;}mozilla::PrintfTarget::PrintfTarget():mEmitted(0){}boolmozilla::PrintfTarget::vprint(constchar*fmt,va_listap){charc;intflags,width,prec,radix,type;union{charch;inti;longl;longlongll;doubled;constchar*s;int*ip;void*p;#if defined(XP_WIN)constwchar_t*ws;#endif}u;constchar*fmt0;staticconstcharhex[]="0123456789abcdef";staticconstcharHEX[]="0123456789ABCDEF";constchar*hexp;inti;charpattern[20];constchar*dolPt=nullptr;// in "%4$.2f", dolPt will point to '.'// Build an argument array, IF the fmt is numbered argument// list style, to contain the Numbered Argument list pointers.NumArgStateVectornas;if(!BuildArgArray(fmt,ap,nas)){// the fmt contains error Numbered Argument format, jliu@netscape.comMOZ_CRASH("Bad format string");}while((c=*fmt++)!=0){if(c!='%'){if(!emit(fmt-1,1))returnfalse;continue;}fmt0=fmt-1;// Gobble up the % format string. Hopefully we have handled all// of the strange cases!flags=0;c=*fmt++;if(c=='%'){// quoting a % with %%if(!emit(fmt-1,1))returnfalse;continue;}if(!nas.empty()){// the fmt contains the Numbered Arguments featurei=0;while(c&&c!='$'){// should improve error check lateri=(i*10)+(c-'0');c=*fmt++;}if(nas[i-1].type==TYPE_UNKNOWN)MOZ_CRASH("Bad format string");ap=nas[i-1].ap;dolPt=fmt;c=*fmt++;}// Examine optional flags. Note that we do not implement the// '#' flag of sprintf(). The ANSI C spec. of the '#' flag is// somewhat ambiguous and not ideal, which is perhaps why// the various sprintf() implementations are inconsistent// on this feature.while((c=='-')||(c=='+')||(c==' ')||(c=='0')){if(c=='-')flags|=FLAG_LEFT;if(c=='+')flags|=FLAG_SIGNED;if(c==' ')flags|=FLAG_SPACED;if(c=='0')flags|=FLAG_ZEROS;c=*fmt++;}if(flags&FLAG_SIGNED)flags&=~FLAG_SPACED;if(flags&FLAG_LEFT)flags&=~FLAG_ZEROS;// widthif(c=='*'){c=*fmt++;width=va_arg(ap,int);if(width<0){width=-width;flags|=FLAG_LEFT;flags&=~FLAG_ZEROS;}}else{width=0;while((c>='0')&&(c<='9')){width=(width*10)+(c-'0');c=*fmt++;}}// precisionprec=-1;if(c=='.'){c=*fmt++;if(c=='*'){c=*fmt++;prec=va_arg(ap,int);}else{prec=0;while((c>='0')&&(c<='9')){prec=(prec*10)+(c-'0');c=*fmt++;}}}// sizetype=TYPE_INTN;if(c=='h'){type=TYPE_SHORT;c=*fmt++;}elseif(c=='L'){type=TYPE_LONGLONG;c=*fmt++;}elseif(c=='l'){type=TYPE_LONG;c=*fmt++;if(c=='l'){type=TYPE_LONGLONG;c=*fmt++;}}elseif(c=='z'||c=='I'){static_assert(sizeof(size_t)==sizeof(int)||sizeof(size_t)==sizeof(long)||sizeof(size_t)==sizeof(longlong),"size_t is not one of the expected sizes");type=sizeof(size_t)==sizeof(int)?TYPE_INTN:sizeof(size_t)==sizeof(long)?TYPE_LONG:TYPE_LONGLONG;c=*fmt++;}// formathexp=hex;switch(c){case'd':case'i':// decimal/integerradix=10;gotofetch_and_convert;case'o':// octalradix=8;type|=1;gotofetch_and_convert;case'u':// unsigned decimalradix=10;type|=1;gotofetch_and_convert;case'x':// unsigned hexradix=16;type|=1;gotofetch_and_convert;case'X':// unsigned HEXradix=16;hexp=HEX;type|=1;gotofetch_and_convert;fetch_and_convert:switch(type){caseTYPE_SHORT:u.l=va_arg(ap,int);if(u.l<0){u.l=-u.l;flags|=FLAG_NEG;}gotodo_long;caseTYPE_USHORT:u.l=(unsignedshort)va_arg(ap,unsignedint);gotodo_long;caseTYPE_INTN:u.l=va_arg(ap,int);if(u.l<0){u.l=-u.l;flags|=FLAG_NEG;}gotodo_long;caseTYPE_UINTN:u.l=(long)va_arg(ap,unsignedint);gotodo_long;caseTYPE_LONG:u.l=va_arg(ap,long);if(u.l<0){u.l=-u.l;flags|=FLAG_NEG;}gotodo_long;caseTYPE_ULONG:u.l=(long)va_arg(ap,unsignedlong);do_long:if(!cvt_l(u.l,width,prec,radix,type,flags,hexp))returnfalse;break;caseTYPE_LONGLONG:u.ll=va_arg(ap,longlong);if(u.ll<0){u.ll=-u.ll;flags|=FLAG_NEG;}gotodo_longlong;caseTYPE_POINTER:u.ll=(uintptr_t)va_arg(ap,void*);gotodo_longlong;caseTYPE_ULONGLONG:u.ll=va_arg(ap,unsignedlonglong);do_longlong:if(!cvt_ll(u.ll,width,prec,radix,type,flags,hexp))returnfalse;break;}break;case'e':case'E':case'f':case'g':u.d=va_arg(ap,double);if(!nas.empty()){i=fmt-dolPt;if(i<int(sizeof(pattern))){pattern[0]='%';memcpy(&pattern[1],dolPt,size_t(i));if(!cvt_f(u.d,pattern,&pattern[i+1]))returnfalse;}}else{if(!cvt_f(u.d,fmt0,fmt))returnfalse;}break;case'c':if((flags&FLAG_LEFT)==0){while(width-->1){if(!emit(" ",1))returnfalse;}}switch(type){caseTYPE_SHORT:caseTYPE_INTN:u.ch=va_arg(ap,int);if(!emit(&u.ch,1))returnfalse;break;}if(flags&FLAG_LEFT){while(width-->1){if(!emit(" ",1))returnfalse;}}break;case'p':type=TYPE_POINTER;radix=16;gotofetch_and_convert;case's':if(type==TYPE_INTN){u.s=va_arg(ap,constchar*);if(!cvt_s(u.s,width,prec,flags))returnfalse;break;}MOZ_ASSERT(type==TYPE_LONG);MOZ_FALLTHROUGH;case'S':#if defined(XP_WIN){u.ws=va_arg(ap,constwchar_t*);intrv=WideCharToMultiByte(CP_ACP,0,u.ws,-1,NULL,0,NULL,NULL);if(rv==0&&GetLastError()==ERROR_NO_UNICODE_TRANSLATION){if(!cvt_s("<unicode errors in string>",width,prec,flags)){returnfalse;}}else{if(rv==0){rv=1;}UniqueFreePtr<char[]>buf((char*)malloc(rv));WideCharToMultiByte(CP_ACP,0,u.ws,-1,buf.get(),rv,NULL,NULL);buf[rv-1]='\0';if(!cvt_s(buf.get(),width,prec,flags)){returnfalse;}}}#else// Not supported here.MOZ_ASSERT(0);#endifbreak;case'n':u.ip=va_arg(ap,int*);if(u.ip){*u.ip=mEmitted;}break;default:// Not a % token after all... skip itif(!emit("%",1))returnfalse;if(!emit(fmt-1,1))returnfalse;}}returntrue;}/************************************************************************/boolmozilla::PrintfTarget::print(constchar*format,...){va_listap;va_start(ap,format);boolresult=vprint(format,ap);va_end(ap);returnresult;}#undef TYPE_SHORT#undef TYPE_USHORT#undef TYPE_INTN#undef TYPE_UINTN#undef TYPE_LONG#undef TYPE_ULONG#undef TYPE_LONGLONG#undef TYPE_ULONGLONG#undef TYPE_STRING#undef TYPE_DOUBLE#undef TYPE_INTSTR#undef TYPE_POINTER#undef TYPE_WSTRING#undef TYPE_UNKNOWN#undef FLAG_LEFT#undef FLAG_SIGNED#undef FLAG_SPACED#undef FLAG_ZEROS#undef FLAG_NEG